1 module hip.audio_decoding.audio; 2 public import hip.api.audio; 3 public import hip.api.data.audio; 4 5 6 7 private const(char)* getNameFromEncoding(HipAudioEncoding encoding) 8 { 9 final switch(encoding) with(HipAudioEncoding) 10 { 11 case MOD: return "mod"; 12 case XM: return "xm"; 13 case FLAC:return "flac"; 14 case MIDI:return "midi"; 15 case MP3:return "mp3"; 16 case OGG:return "ogg"; 17 case WAV:return "wav"; 18 } 19 } 20 21 22 23 T[] monoToStereo(T)(T[] data) 24 { 25 T[] ret = new T[data.length*2]; 26 for(size_t i = 0; i < data.length; i++) 27 { 28 ret[i*2] = data[i]; 29 ret[i*2+1] = data[i]; 30 } 31 return ret; 32 } 33 34 T[][2] stereoToMonoSplit(T)(T[] data) 35 { 36 assert(data.length % 2 == 0, "Stereo to mono data must be divisible by 2"); 37 38 size_t len2 = cast(size_t)(data.length / 2); 39 T[][2] ret = [new T[len2], new T[len2]]; 40 41 for(size_t i = 0; i < data.length; i++) 42 { 43 ret[i%2][cast(size_t)(i/2)] = data[i]; 44 } 45 46 return ret; 47 } 48 49 T[] stereoToMono(T)(T[] data) 50 { 51 assert(data.length % 2 == 0, "Stereo to mono data must be divisible by 2"); 52 T[] ret = new T[cast(size_t)(data.length / 2)]; 53 54 size_t retIdx = 0; 55 for(size_t i = 0; i < data.length; i+= 2) 56 { 57 ret[retIdx++] = cast(T)(cast(float)((data[i] + data[i+1]) / 2)); 58 } 59 60 return ret; 61 } 62 63 private abstract class AHipAudioDecoder : IHipAudioDecoder 64 { 65 float sampleRate; 66 int channels; 67 protected float duration; 68 protected size_t clipSize; 69 ubyte getClipChannels(){return cast(ubyte)channels;} 70 size_t getClipSize(){return clipSize;} 71 float getDuration(){return duration;} 72 uint getSamplerate(){return cast(uint)sampleRate;} 73 AudioConfig getAudioConfig(){return AudioConfig(getSamplerate, AudioFormat.init, getClipChannels, cast(int)getClipSize);} 74 bool resample(in ubyte[] data, HipAudioType type, uint outputSampleRate, uint outputChannels){return false;} 75 bool channelConversion(in ubyte[] data, ubyte from, ubyte to){return false;} 76 } 77 78 79 version(AudioFormatsDecoder) 80 { 81 private struct _AudioStreamImpl 82 { 83 void[] block; 84 void initialize() 85 { 86 import audioformats; 87 block = new void[AudioStream.sizeof]; 88 } 89 90 void openFromMemory(in ubyte[] inputData) 91 { 92 if(block == null) 93 initialize(); 94 import audioformats; 95 (cast(AudioStream*)block).openFromMemory(inputData); 96 } 97 long getLengthInFrames() 98 { 99 import audioformats; 100 return (cast(AudioStream*)block).getLengthInFrames(); 101 } 102 int getNumChannels() 103 { 104 import audioformats; 105 return (cast(AudioStream*)block).getNumChannels(); 106 } 107 float getSamplerate() 108 { 109 import audioformats; 110 return (cast(AudioStream*)block).getSamplerate(); 111 } 112 int readSamplesFloat(float[] outputBuffer) 113 { 114 import audioformats; 115 return (cast(AudioStream*)block).readSamplesFloat(outputBuffer); 116 } 117 void cleanUp() 118 { 119 import audioformats; 120 if(block != null) 121 { 122 AudioStream as = *(cast(AudioStream*)block); //Make it execute the destructor here 123 destroy(block); 124 block = null; 125 } 126 } 127 } 128 129 class HipAudioFormatsDecoder : AHipAudioDecoder 130 { 131 ///This is where the compressed data will actually be stored and from where we get the decoded data 132 private _AudioStreamImpl input; 133 134 135 ///Intermediary buffer where the decoded buffer will be put. 136 protected float[] chunkBuffer; 137 138 ///Probably this will be deprecated 139 protected uint chunkSize; 140 141 ///This can hold the entire audio buffer or only enough for playing 142 float[] decodedBuffer; 143 144 145 /** 146 * Will completely decode all the data. 147 * Returns if the decode was successful. 148 * The decoded data can be retrieved by calling `getClipData()` 149 */ 150 bool decode(in ubyte[] data, HipAudioEncoding encoding, HipAudioType type, 151 void delegate(in ubyte[]) onSuccess, void delegate() onFailure) 152 { 153 import audioformats : audiostreamUnknownLength; 154 input.openFromMemory(cast(ubyte[])data); 155 156 long lengthFrames = input.getLengthInFrames(); 157 channels = input.getNumChannels(); 158 sampleRate = input.getSamplerate(); 159 160 bool decodeSuccesful; 161 162 if(lengthFrames == audiostreamUnknownLength) ///? Streamed audio 163 { 164 uint bytesRead = 0; 165 166 decodedBuffer.length = audioConfigDefaultBufferSize; 167 ubyte[] output = cast(ubyte[])decodedBuffer; 168 startDecoding(data, output, audioConfigDefaultBufferSize, encoding); 169 while((bytesRead = updateDecoding(output)) != 0) 170 { 171 bytesRead/= float.sizeof; 172 decodedBuffer.length+= bytesRead; 173 output = cast(ubyte[])decodedBuffer[$-bytesRead..$]; 174 } 175 duration = (clipSize / channels) / sampleRate; 176 } 177 else 178 { 179 duration = lengthFrames/cast(double)sampleRate; 180 size_t bufferSize = cast(size_t)(lengthFrames*channels); 181 decodedBuffer = new float[bufferSize]; 182 int bytesRead = input.readSamplesFloat(decodedBuffer); 183 184 clipSize = decodedBuffer.length*float.sizeof; 185 decodeSuccesful = bytesRead == lengthFrames; 186 } 187 input.cleanUp(); 188 decodeSuccesful ? onSuccess(getClipData) : onFailure(); 189 190 return decodeSuccesful; 191 } 192 uint startDecoding(in ubyte[] data, ubyte[] outputDecodedData, uint chunkSize, HipAudioEncoding encoding) 193 { 194 input.openFromMemory(cast(ubyte[])data); 195 channels = input.getNumChannels(); 196 sampleRate = input.getSamplerate(); 197 198 chunkSize = (chunkSize / channels) / float.sizeof; 199 chunkBuffer = new float[chunkSize]; 200 this.chunkSize = chunkSize; 201 202 return updateDecoding(outputDecodedData); 203 } 204 uint updateDecoding(ubyte[] outputDecodedData) 205 { 206 import core.stdc.string:memcpy; 207 int framesRead = 0; 208 uint currentRead = 0; 209 do 210 { 211 framesRead = input.readSamplesFloat(chunkBuffer); 212 assert(framesRead * channels * float.sizeof < outputDecodedData.length, "Out of boundaries decoding"); 213 if(framesRead != 0) 214 memcpy(outputDecodedData.ptr + currentRead, 215 chunkBuffer.ptr, framesRead * channels * float.sizeof); 216 217 currentRead += framesRead * channels * float.sizeof; 218 } while(framesRead > 0 && currentRead <= chunkSize); 219 220 221 clipSize+= currentRead; 222 return currentRead; 223 } 224 225 override AudioConfig getAudioConfig(){return AudioConfig(getSamplerate, AudioFormat.float32Little, getClipChannels, cast(int)getClipSize);} 226 227 228 void dispose(){input.cleanUp();} 229 230 override bool resample(in ubyte[] data, HipAudioType type, uint outputSampleRate, uint outputChannels, 231 void delegate(in ubyte[]) onSuccess, void delegate()) 232 { 233 version(none) 234 { 235 static assert(false, "Incomplete implementation."); 236 auto resampler = new HipAllResample( 237 getClipData(), 238 cast(int)sampleRate, 239 cast(int)outputSampleRate, 240 channels, 241 1 242 ); 243 244 float resampleRate = cast(float)outputSampleRate / cast(float)sampleRate; 245 float[] resampledBuffer = new float[cast(ulong)(decodedBuffer.length * resampleRate)]; 246 resampledBuffer[] = 0; 247 248 do{ 249 250 import std.stdio; 251 writeln("Resampled!"); 252 } 253 while(resampler.fillBuffer(resampledBuffer)); 254 255 decodedBuffer = resampledBuffer; 256 clipSize = decodedBuffer.length * float.sizeof; 257 import std.stdio; 258 writeln("Converted from ",cast(int)sampleRate, "Hz to ", outputSampleRate, "Hz"); 259 260 } 261 onSuccess(getClipData); 262 263 return true; 264 } 265 override bool channelConversion(in ubyte[] data, ubyte from, ubyte to) 266 { 267 if(to == 2 && from == 1) 268 { 269 decodedBuffer = monoToStereo!(float)(cast(float[])data); 270 channels = to; 271 clipSize*= 2; 272 return true; 273 } 274 else if(to == 1 && from == 2) 275 { 276 decodedBuffer = stereoToMono!(float)(cast(float[])data); 277 channels = to; 278 clipSize/= 2; 279 return true; 280 } 281 return false; 282 } 283 284 ubyte[] getClipData(){return cast(ubyte[])decodedBuffer;} 285 } 286 } 287 288 version(WebAssembly) 289 { 290 import hip.wasm; 291 292 private alias WasmAudioBuffer = size_t; 293 extern(C) WasmAudioBuffer WasmDecodeAudio(size_t length, void* ptr, JSDelegateType!(void) dg); 294 extern(C) size_t WasmGetClipChannels(WasmAudioBuffer); 295 extern(C) size_t WasmGetClipSize(WasmAudioBuffer); 296 extern(C) double WasmGetClipDuration(WasmAudioBuffer); 297 extern(C) float WasmGetClipSamplerate(WasmAudioBuffer); 298 299 class HipWebAudioDecoder : AHipAudioDecoder 300 { 301 302 WasmAudioBuffer buffer; 303 304 bool decode(in ubyte[] data, HipAudioEncoding encoding, HipAudioType type, 305 void delegate(in ubyte[] data) onSuccess, void delegate() onFailure) 306 { 307 buffer = WasmDecodeAudio(data.length, cast(void*)data.ptr, sendJSDelegate!((WasmAudioBuffer buff) 308 { 309 assert(buff == buffer, "Different object returned from audio decoded."); 310 311 channels = WasmGetClipChannels(buffer); 312 clipSize = WasmGetClipSize(buffer); 313 duration = WasmGetClipDuration(buffer); 314 sampleRate = WasmGetClipSamplerate(buffer); 315 onSuccess(getClipData); 316 }).tupleof); 317 return false; 318 } 319 320 ///Unsupported at the moment 321 uint startDecoding(in ubyte[] data, ubyte[] outputDecodedData, uint chunkSize, HipAudioEncoding encoding){return 0;} 322 uint updateDecoding(ubyte[] outputDecodedData){return 0;} 323 /// 324 325 ///Returns the buffer handle. 326 ubyte[] getClipData() 327 { 328 ubyte* ptr = cast(ubyte*)&buffer; 329 return ptr[0..WasmAudioBuffer.sizeof]; 330 } 331 332 void dispose(){} 333 } 334 } 335 336 337 class HipNullAudioDecoder: IHipAudioDecoder 338 { 339 bool decode(in ubyte[] data, HipAudioEncoding encoding, HipAudioType type, 340 void delegate(in ubyte[]), void delegate()){return false;} 341 342 bool resample(in ubyte[] data, HipAudioType type, uint outputSampleRate, uint outputChannels, 343 void delegate(in ubyte[]), void delegate()){return false;} 344 345 bool channelConversion(in ubyte[] data, ubyte from, ubyte to){return false;} 346 uint startDecoding(in ubyte[] data, ubyte[] outputDecodedData, uint chunkSize, HipAudioEncoding encoding){return 0;} 347 uint updateDecoding(ubyte[] outputDecodedData){return 0;} 348 AudioConfig getAudioConfig(){return AudioConfig.init;} 349 ubyte[] getClipData(){return null;} 350 size_t getClipSize(){return 0;} 351 float getDuration(){return 0;} 352 void dispose(){} 353 uint getSamplerate(){return 0;} 354 ubyte getClipChannels(){return 0;} 355 } 356 357 358 version(none) //Buggy and not currently working 359 { 360 import hip.audio_decoding.resampler; 361 package class HipAllResample : ResamplingContext 362 { 363 enum BYTES_PER_LOAD = 4096; 364 365 int resampledSamples = 0; 366 float[] decodedData; 367 368 this(ubyte[] decodedData, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) 369 { 370 super(new SampleControlFlags(), inputSampleRate, outputSampleRate, inputChannels, outputChannels); 371 this.decodedData = cast(float[])decodedData; 372 } 373 override void loadMoreSamples() @trusted 374 { 375 376 int toResample; 377 ///Do it BYTES_PER_LOAD as step 378 if(resampledSamples + BYTES_PER_LOAD <= decodedData.length ) 379 toResample = BYTES_PER_LOAD; 380 else //If it overflows, clamp it to the minimum 381 toResample = cast(int)(decodedData.length) - resampledSamples; 382 383 384 resamplerDataLeft.dataIn = cast(float[])(decodedData[resampledSamples .. resampledSamples+toResample]); 385 resamplerDataRight.dataIn = cast(float[])(decodedData[resampledSamples .. resampledSamples+toResample]); 386 resampledSamples+= toResample; 387 388 } 389 } 390 } 391 392 version(WebAssembly) 393 alias HipAudioDecoder = HipWebAudioDecoder; 394 else version(AudioFormatsDecoder) 395 alias HipAudioDecoder = HipAudioFormatsDecoder; 396 else 397 { 398 alias HipAudioDecoder = HipNullAudioDecoder; 399 pragma(msg, "WARNING: Using NullAudioDecoder"); 400 }